#define ENCRYPT
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data;
using System.Security;
using System.Security.Cryptography;
using System.Xml;

#region Copyright (c) 2006, 2007, 2008 ViewOn Media Services. All rights reserved.

/*
 Copyright (c) 2006, 2007, 2008 ViewOn Media Services. All rights reserved.
 This work is copyrighted in its entirety. Permission to use "ViewOn.tv"
 material may be granted only after we receive a written request from you,
 and you receive written permission from us.

 While ViewOn Media Services provides the information herein to anyone,
 we retain copyright on all code, text, graphic images, and other content.
 This means that you may not distribute the code, the text or graphics to others without the
 express written permission of ViewOn Media Services; "mirror" this information on your server
 without our permission; or modify or reuse the code, text or graphics on our servers.
 You may print copies of the information for your own personal use; and, you may reference
 the Service from your own documents. Any commercial use of this material is prohibited without
 the written permission of ViewOn Media Services. In all copies of this information, you must
 retain this notice and any other copyright notices originally included with such information.

 This material may be protected by U.S. and International Copyright laws, and you are legally
 bound by the copyright notices on those systems.

 "ViewOn" logos and other official symbols or graphics may not be used in connection with any
 product or service that has not been expressly authorized in advance in writing or in any manner
 that is likely to cause confusion among the users of this or other Web sites or among the public.
 All other seals, graphics or trademarks of organizations not affiliated with ViewOn Media Services
 that appear on this site are the property of their respective owners.

 ViewOn Media Services reserve all other rights. 
*/

#endregion

#region ViewOn.tv Source Code Licence

/*
This license governs use of the ViewOn.tv software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

1. Summary

The source code is made available to view for reference purposes or to contribute to the project only. Developers may not distribute or modify the code for commercial or non-commercial purposes without express Agreement of View On Media Services.

2. Definitions

The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
"Licensor" means the company "ViewOn Media Services".
"You" means the licensee of the software, who is not engaged in designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software.
"Your company" means the company you worked for when you downloaded the software.
"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging and maintaining your products. For clarity, "reference use" does NOT include (a) the right to use the software for purposes of designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software, and (b) the right to distribute the software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

3. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.
(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.
4. Limitations
(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */

#endregion

namespace ViewOnAddInsAPI.Helper.Security
{
	/// <summary>
	/// Provides methods to save and read a  an encrypted XML file.
	/// 
	/// Main functionality:
	/// -------------------
	/// 1. Save any DataSet to an encrypted XML file.
	/// 2. Read the encrypted XML file back and present it to any program as a DataSet, 
	///    without creating temporary files in the process
	/// 3. The encrypted XML files are marked with a signature that identifies them as being encrypted files.
	/// 
	/// Limitations:
	/// ------------
	/// 1. The class uses a user name and password to generate a combined key which is used for encryption. 
	///    No methods are provided to manage the secure storage of these data.
	/// 2. Theoretically, the class can handle files up to 2GB in size. In practice, since conversions 
	///    are handled in memory to avoid having temporary (decrypted) files being written to the drive, 
	///    the practical size may be limited by available system resources.
	/// </summary>
	sealed public class XmlEncrypter
	{

		#region Variables

		private byte[] signature;
		private string _key;

		const int BIN_SIZE = 4096;
		private byte[] md5Key, md5IV;
		private bool validParameters;

		#endregion

		#region constructor

		public XmlEncrypter(string key)
		{
			_key = key;

			if (key.Length < 6)
			{
				validParameters = false;
				// abort the constructor. Calls to public functions will not work.
				return;
			}
			else
				validParameters = true;

			GenerateSignature();
			GenerateKey();
			GenerateIV();
		}

		#endregion

		#region Helper functions called from constructor only

		/// <summary>
		/// Generates a standard signature for the file. The signature may be longer than 16 bytes if deemed necessary.
		/// The signature, which is NOT ENCRYPTED, serves two purposes. 
		/// 1. It allows to recognize the file as one that has been encrypted with the XMLEncryptor class.
		/// 2. The first bytes of each XML file are quite similar (<?xml version="1.0" encoding="utf-8" ?>).
		///	 This can be exploite to "guess" the key the file has been encrypted with. Adding a signature of a reasonably
		///	 large number of bytes can be used to overcome this limitation.
		/// </summary>
		private void GenerateSignature()
		{
			signature = new byte[16] {
												 123,	078,	099,	166,
												 000,	043,	244,	008,
												 005,	089,	239,	255,
												 045,	188,	007,	033
											 };
		}

		/// <summary>
		/// Generates an MD5 key for encryption/decryption. This method is only called during construction.
		/// </summary>
		private void GenerateKey()
		{
			MD5 md5 = new MD5CryptoServiceProvider();
			StringBuilder hash = new StringBuilder(_key);

			// Manipulate the hash string - not strictly necessary.
			for (int i = 1; i < hash.Length; i += 2)
			{
				char c = hash[i - 1];
				hash[i - 1] = hash[i];
				hash[i] = c;
			}

			// Convert the string into a byte array.
			Encoding unicode = Encoding.Unicode;
			byte[] unicodeBytes = unicode.GetBytes(hash.ToString());

			// Compute the key from the byte array
			md5Key = md5.ComputeHash(unicodeBytes);
		}

		/// <summary>
		/// Generates an MD5 Initialization Vector for encryption/decryption. This method is only called during construction.
		/// </summary>
		private void GenerateIV()
		{
			MD5 md5 = new MD5CryptoServiceProvider();
			string hash = _key;

			// Convert the string into a byte array.
			Encoding unicode = Encoding.Unicode;
			byte[] unicodeBytes = unicode.GetBytes(hash);

			// Compute the IV from the byte array
			md5IV = md5.ComputeHash(unicodeBytes);
		}


		#endregion

		#region Methods to write and verify the signature

		private void WriteSignature(FileStream fOut)
		{
			fOut.Position = 0;
			fOut.Write(signature, 0, 16);
		}

		private bool VerifySignature(FileStream fIn)
		{
			byte[] bin = new byte[16];
			fIn.Read(bin, 0, 16);
			for (int i = 0; i < 16; i++)
			{
				if (bin[i] != signature[i])
					return false;
			}
			// Reset file pointer.
			fIn.Position = 0;
			return true;
		}


		#endregion

		#region Public LoadEncryptedXML

		/// <summary>
		/// Reads an encrypted XML file.
		/// </summary>
		/// <param name="fileName">The path to the XML file.</param>
		/// <returns>The DataSet, or null if an error occurs.</returns>
		public XmlDocument LoadEncryptedXML(string fileName)
		{
#if !ENCRYPT
			XmlDocument xmlDocument = new XmlDocument();
			try
			{
				xmlDocument.Load(fileName);
			}
			catch
			{
				return null;
			}

			return xmlDocument;
#endif

			FileInfo fi = new FileInfo(fileName);
			FileStream inFile;

			#region Check for possible errors (includes verification of the signature).
			if (!validParameters)
			{
				Trace.WriteLine("Invalid parameters - cannot perform requested action");
				return null;
			}
			if (!fi.Exists)
			{
				Trace.WriteLine("Cannot perform decryption: File " + fileName + " does not exist.");
				return null;
			}
			if (fi.Length > Int32.MaxValue)
			{
				Trace.WriteLine("This decryption method can only handle files up to 2GB in size.");
				return null;
			}

			try
			{
				inFile = new FileStream(fi.FullName, FileMode.Open);
			}
			catch (Exception exc)
			{
				Trace.WriteLine(exc.Message + "Cannot perform decryption");
				return null;
			}
			if (!VerifySignature(inFile))
			{
				Trace.WriteLine("Invalid signature - file was not encrypted using this program");
				return null;
			}
			#endregion

			RijndaelManaged rijn = new RijndaelManaged();
			rijn.Padding = PaddingMode.Zeros;
			ICryptoTransform decryptor = rijn.CreateDecryptor(md5Key, md5IV);

			// Allocate byte array buffer to read only the xml part of the file (ie everything following the signature).
			byte[] encryptedXmlData = new byte[(int)fi.Length - signature.Length];
			inFile.Position = signature.Length;
			inFile.Read(encryptedXmlData, 0, encryptedXmlData.Length);

			// Convert the byte array to a MemoryStream object so that it can be passed on to the CryptoStream
			MemoryStream encryptedXmlStream = new MemoryStream(encryptedXmlData);

			// Create a CryptoStream, bound to the MemoryStream containing the encrypted xml data
			CryptoStream csDecrypt = new CryptoStream(encryptedXmlStream, decryptor, CryptoStreamMode.Read);

			// Read in the DataSet from the CryptoStream
			XmlDocument document = new XmlDocument();
			try
			{
				document.Load(csDecrypt);
			}
			catch (Exception exc)
			{
				Trace.WriteLine(exc.Message, "Error decrypting XML");
				return null;
			}

			// flush & close files.
			encryptedXmlStream.Flush();
			encryptedXmlStream.Close();
			inFile.Close();

			return document;
		}

		#endregion

		#region SaveEncryptedXML

		/// <summary>
		/// Save to an encrypted file.
		/// </summary>
		/// <param name="dataset">The DataSet to encrypt.</param>
		/// <param name="encFileName">The name of the encrypted file. Existing files will be overwritten.</param>
		public void SaveEncryptedXML(XmlDocument document, string encFileName)
		{
#if !ENCRYPT
			document.Save(encFileName);
			return;
#endif

			FileStream fOut;

			#region Check for possible errors
			if (!validParameters)
			{
				Trace.WriteLine("Invalid parameters - cannot perform requested action");
				return;
			}
			#endregion

			// Create a MemoryStream and write the DataSet to it.
			MemoryStream xmlStream = new MemoryStream();
			document.Save(xmlStream);

			// Reset the pointer of the MemoryStream (which is at the EOF after the WriteXML function).
			xmlStream.Position = 0;

			// Create a write FileStream and write the signature to it (unencrypted).
			fOut = new FileStream(encFileName, FileMode.Create);
			WriteSignature(fOut);

			#region Encryption objects
			RijndaelManaged rijn = new RijndaelManaged();
			rijn.Padding = PaddingMode.Zeros;
			ICryptoTransform encryptor = rijn.CreateEncryptor(md5Key, md5IV);
			CryptoStream csEncrypt = new CryptoStream(fOut, encryptor, CryptoStreamMode.Write);
			#endregion

			// Create variables to help with read and write.
			byte[] bin = new byte[BIN_SIZE];			// Intermediate storage for the encryption.
			int rdlen = 0;									// The total number of bytes written.
			int totlen = (int)xmlStream.Length;	// The total length of the input stream.
			int len;											// The number of bytes to be written at a time.

			// Read from the input file, then encrypt and write to the output file.
			while (rdlen < totlen)
			{
				len = xmlStream.Read(bin, 0, bin.Length);
				if (len == 0 && rdlen == 0)
				{
					Trace.WriteLine("No read");
					break;
				}
				csEncrypt.Write(bin, 0, len);
				rdlen += len;
			}

			csEncrypt.FlushFinalBlock();
			csEncrypt.Close();
			fOut.Close();
			xmlStream.Close();
		}

		#endregion

	}
}
